package cc.rengu.redp.base.authentication;

import cc.rengu.redp.base.domain.ActiveUser;
import cc.rengu.redp.common.domain.RedpConstant;
import cc.rengu.redp.common.exception.RedisConnectException;
import cc.rengu.redp.common.properties.RedpProperties;
import cc.rengu.redp.common.redis.RedisService;
import cc.rengu.redp.common.utils.DateUtil;
import cc.rengu.redp.common.utils.SpringContextUtil;
import cc.rengu.redp.system.domain.User;
import cc.rengu.redp.system.manager.UserManager;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.Set;

@Slf4j
public class JWTRefresh {

    private static UserManager userManager = SpringContextUtil.getBean(UserManager.class);
    private static RedisService redisService = SpringContextUtil.getBean(RedisService.class);

    public static final String TOKEN_UPDATE = "token-update";
    public static final String TOKEN_EXP_UPDATE = "token-exp-update";

    // 单位（秒）
    private static final long REFRESH_TIME = SpringContextUtil.getBean(RedpProperties.class).getShiro().getJwtRefreshTime();
    private static final long EXPIRE_TIME = SpringContextUtil.getBean(RedpProperties.class).getShiro().getJwtTimeOut();
    private static final long GRACE_LIMIT = SpringContextUtil.getBean(RedpProperties.class).getShiro().getJwtGraceLimit();
    public static final Boolean USER_MSG_REFRESH = SpringContextUtil.getBean(RedpProperties.class).getShiro().getJwtUserMessageRefresh();

    public static JWTTokenInfo getNewToken(String oldTokenDecrypt, String oldTokenEncrypt, String ip) {
        if (REFRESH_TIME <= 0) return null;
        if (REFRESH_TIME >= EXPIRE_TIME) return null;
        Date issueDate = JWTUtil.getIssueDate(oldTokenDecrypt);
        if (issueDate == null) return null;
        LocalDateTime ldt = DateUtil.dateToLocalDateTime(issueDate);
        if (LocalDateTime.now().minusSeconds(REFRESH_TIME).compareTo(ldt) > 0) {
            return signUpdateToken(oldTokenDecrypt, oldTokenEncrypt, ip);
        }

        return null;
    }

    private static JWTTokenInfo signUpdateToken(String oldTokenDecrypt, String oldTokenEncrypt, String ip) {
        //限流处理，避免用户一次操作同时发起多个请求造成不断签发新Token
        try {
            String limitFlg = redisService.get(RedpConstant.TOKEN_CACHE_LIMIT_PREFIX + JWTUtil.encryptToken(oldTokenEncrypt) + StringPool.DOT + ip);
            if (StringUtils.isNotBlank(limitFlg)) return null;
            redisService.set(RedpConstant.TOKEN_CACHE_LIMIT_PREFIX + oldTokenEncrypt + StringPool.DOT + ip, oldTokenEncrypt, GRACE_LIMIT * 1000);
        } catch (RedisConnectException e) {
            log.error("RedisConnectException", e);
            return null;
        }

        //获取当前活动用户
        String now = DateUtil.formatFullTime(LocalDateTime.now());
        Set<String> userOnlineStringSet = null;
        try {
            userOnlineStringSet = redisService.zrangeByScore(RedpConstant.ACTIVE_USERS_ZSET_PREFIX, now, "+inf");
        } catch (RedisConnectException e) {
            log.error("RedisConnectException", e);
            return null;
        }

        String userJsonString = "";
        ActiveUser activeUser = null;
        for (String userOnlineString : userOnlineStringSet) {
            activeUser = JSONObject.parseObject(userOnlineString, ActiveUser.class);
            if (StringUtils.equals(activeUser.getToken(), oldTokenEncrypt)) {
                userJsonString = userOnlineString;
                break;
            }
        }

        if (activeUser != null && StringUtils.isNotBlank(userJsonString)) {
            //构建新token
            String username = JWTUtil.getUsername(oldTokenDecrypt);
            User user = userManager.getUser(username);
            String updatedToken = JWTUtil.encryptToken(JWTUtil.sign(username, user.getPassword()));

            //构建新的JWTTokenInfo
            LocalDateTime expireTime = LocalDateTime.now().plusSeconds(EXPIRE_TIME);
            String expireTimeStr = DateUtil.formatFullTime(expireTime);
            JWTTokenInfo jwtTokenInfo = new JWTTokenInfo(updatedToken, expireTimeStr);

            //更新活动用户信息，清理旧token信息，添加新token信息
            try {
                redisService.zrem(RedpConstant.ACTIVE_USERS_ZSET_PREFIX, userJsonString);
                // 旧token保留一段时间，避免用旧token在同一时间内发起的多个请求无法验证
                redisService.pexpire(RedpConstant.TOKEN_CACHE_PREFIX + oldTokenEncrypt + StringPool.DOT + ip, GRACE_LIMIT * 1000);
                // 更新token
                activeUser.setToken(updatedToken);
                // zset 存储登录用户，score 为过期时间戳
                redisService.zadd(RedpConstant.ACTIVE_USERS_ZSET_PREFIX, Double.valueOf(jwtTokenInfo.getExpireAt()), JSONObject.toJSONString(activeUser));
                // redis 存储加密token
                redisService.set(RedpConstant.TOKEN_CACHE_PREFIX + updatedToken + StringPool.DOT + ip, updatedToken, EXPIRE_TIME * 1000);
            } catch (RedisConnectException e) {
                log.error("RedisConnectException", e);
                return null;
            }

            return jwtTokenInfo;
        }

        return  null;
    }
}
